/*
XPTracker - Alternative Agile Tracking Tool
Copyright (C) 2006-2009  Stephen Starkey

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/
package org.xptracker.search;

import com.google.inject.Inject;
import net.sourceforge.stripes.validation.ValidationError;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.Hit;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.xptracker.domain.Domain;
import org.xptracker.domain.Story;
import org.xptracker.domain.WikiPage;
import org.xptracker.persistence.PersistenceManager;
import org.xptracker.util.BetterDateTypeConverter;
import org.xptracker.util.ThreadLocalCleaner;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LuceneSearcher implements Searcher {
  private static final Pattern DATE_PATTERN = Pattern.compile("date\\{(.*?)\\}");

  private LuceneServices services;
  private IndexingHelper indexingHelper;
  private PersistenceManager persistenceManager;

  private static final Map<Class, ItemRetriever> RETRIEVERS = new HashMap<Class, ItemRetriever>() {
    {
      put(Story.class, new StoryRetriever());
      put(WikiPage.class, new WikiPageRetriever());
    }
  };

  @Inject
  public LuceneSearcher(LuceneServices services, IndexingHelper indexingHelper,
                        PersistenceManager persistenceManager) {
    super();
    this.services = services;
    this.indexingHelper = indexingHelper;
    this.persistenceManager = persistenceManager;
  }

  public <T> List<T> search(Class<T> type, final String query) {
    final ItemRetriever<T> retriever = RETRIEVERS.get(type);

    services.withSearcher(new IndexSearchHandler() {
      public void handleSearcher(IndexSearcher searcher, Analyzer analyzer) throws ParseException, IOException {
        MultiFieldQueryParser parser = new MultiFieldQueryParser(indexingHelper.getQueryFields(retriever.getType()),
            analyzer);
        Query q = parser.parse(replaceMacros(query));
        final Hits hits = searcher.search(q);
        Iterator<Hit> hitsI = hits.iterator();
        while (hitsI.hasNext()) {
          try {
            Document doc = hitsI.next().getDocument();
            retriever.hit(indexingHelper.getId(doc));
          } catch (IOException e) {
            throw new RuntimeException(e);
          }
        }
      }
    });

    return retriever.loadItems(new Domain(persistenceManager));
  }

  private static abstract class ItemRetriever<T> {
    private ThreadLocal<List<Long>> ids = ThreadLocalCleaner.registerForCleanup(new ThreadLocal<List<Long>>() {
      protected List<Long> initialValue() {
        return new LinkedList<Long>();
      }
    });

    final List<T> loadItems(Domain d) {
      final LinkedList<T> list = new LinkedList<T>(loadItems(d, ids.get().toArray(new Long[0])));
      ids.remove();
      return list;
    }

    public final void hit(String id) {
      ids.get().add(new Long(id));
    }

    abstract Collection<? extends T> loadItems(Domain d, Long[] ids);

    abstract Class<T> getType();
  }

  private static class StoryRetriever extends ItemRetriever<Story> {
    public Class<Story> getType() {
      return Story.class;
    }

    Collection<? extends Story> loadItems(Domain d, Long[] ids) {
      return d.loadStories(ids);
    }
  }

  private static class WikiPageRetriever extends ItemRetriever<WikiPage> {
    public Class<WikiPage> getType() {
      return WikiPage.class;
    }

    Collection<? extends WikiPage> loadItems(Domain d, Long[] ids) {
      return d.loadWikiPages(ids);
    }
  }

  private String replaceMacros(String query) {
    DateFormat fmt = new SimpleDateFormat("yyyyMMdd");
    StringBuffer result = new StringBuffer();
    BetterDateTypeConverter converter = new BetterDateTypeConverter();
    converter.setLocale(Locale.getDefault());
    Matcher m = DATE_PATTERN.matcher(query);
    while (m.find()) {
      String dateStr = m.group(1);

      Date s = converter.convert(dateStr, Date.class, new ArrayList<ValidationError>());
      if (s != null) {
        m.appendReplacement(result, fmt.format(s));
      } else {
        m.appendReplacement(result, fmt.format(new Date()));
        LogFactory.getLog(getClass()).error("Unparseable date: " + dateStr);
      }
    }
    m.appendTail(result);
    return result.toString();
  }
}
